#include "StdAfx.h"
#include <MyControls/TimeRuler.h>
#include <MyControls/Timeline.h>
#include <MyControls/App.h>
#include <MyControls/Resource.h>
#include <MyFC/Utility.h>
#include <cmath>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

namespace My {

// Thumb

TimeRuler::Thumb::Thumb(TimeRuler & r)
	: m_TimeRuler(r), m_Timeline(r.m_Timeline) 
{ 
	m_TimeRuler.Add(this);
}

CRect TimeRuler::Thumb::Bounds() const
{
	return AlignRect(
		CRect(0, 0, m_TimeRuler.m_ThumbSize, m_TimeRuler.Height()),
		CPoint(m_Timeline.TimeToX(m_Timeline.GetCurrentFrame()), -1),
		ALIGN_HCENTER|ALIGN_VTOP);
}

bool TimeRuler::Thumb::Visible() const
{
	return !m_Timeline.IsEmpty();
}

bool TimeRuler::Thumb::HandleDragStart(unsigned int flags, const CPoint & p0, const CPoint & p)
{
	if (m_Timeline.IsEmpty())
		return false;
	m_StartPos = ClientToParent(p0).x;
	m_StartValue = m_Timeline.TimeToX(m_Timeline.GetCurrentFrame());
	return true;
}

void TimeRuler::Thumb::HandleDragEnd(unsigned int flags, const CPoint & p)
{
	m_Timeline.OnAutoScroll(false);
	m_Timeline.SetTimeAutoScroll(0);
	m_Timeline.UpdateCurrentFrame();
}

void TimeRuler::Thumb::HandleDragging(unsigned int flags, const CPoint & p)
{
	int x = m_StartValue + ClientToParent(p).x - m_StartPos;
	m_Timeline.SetCurrentFrame(Round(m_Timeline.XToTime(x)));

	// Autoscroll?
	x -= m_StartValue - m_StartPos;
	if (x < m_Timeline.GetGap())
		m_Timeline.SetTimeAutoScroll(x - m_Timeline.GetGap());
	else if (x > m_TimeRuler.Width() - m_Timeline.GetGap())
		m_Timeline.SetTimeAutoScroll(x - m_TimeRuler.Width() + m_Timeline.GetGap());
	else
		m_Timeline.SetTimeAutoScroll(0);
}

// TimeRuler

TimeRuler::TimeRuler(Timeline & t) : 
	m_Timeline(t),
	m_Thumb(* this)
{
	m_ThumbSize = 15;
}

String TimeRuler::Hint() const
{
	return GetApp().LoadString(IDS_TIMELINE_RULER);
}

CSize TimeRuler::GetPreferredSize() const
{
	CDC dc;
	dc.CreateCompatibleDC(0);
	SaveObjects save(&dc);
	SelectFont(&dc);
	return CSize(0, dc.GetTextExtent("A").cy + 8);
}

CString TimeRuler::GetLabel(int frame)
{
	if (m_Timeline.IsEmpty())
		return "";
	CString s;
	s.Format("%02df", frame);
	return s;
}

void TimeRuler::UpdateScale()
{
	if (m_Timeline.IsEmpty())
	{
		m_Span = 1;
		Invalidate();
		return;
	}

	CDC dc;
	dc.CreateCompatibleDC(0);
	SaveObjects save(&dc);
	SelectFont(&dc);

	m_Span = 1;
	double span, span10;
	// a few iterations
	for (int i = 0; i < 5; ++i) {
		int textw = 35;
		int j0 = int(ceil(m_Timeline.GetTimeOffset())) / m_Span;
		for (int j = 0; j < 5; ++j) 
		{
			int frame = m_Span * (j0 + j);
			int tw = dc.GetTextExtent(GetLabel(frame)).cx + 4;
			if (tw > textw)
				textw = tw;
		}
		span = fabs(textw / m_Timeline.GetTimeScale()); 
		span10 = pow(10, int(ceil(log10(span))));
		if (span10 <= 1.0)
			span = 1.0;
		else
			if (span < span10 / 5)
				span = span10 / 5;
			else
				if (span < span10 / 2)
					span = span10 / 2;
				else
					span = span10;
		if (m_Span == span)
			break;
		m_Span = (int) span;
	}

	Invalidate();
}

void TimeRuler::ProcessEvent(Event & e)
{
	if (e.Is<Sequence::PreviewFrameChange>())
		return;
	if (e.Is<Sequence::Event>()) {
		UpdateScale();
		Invalidate();
	} else
	if (e.Is<Timeline::TimeScaleChange>()) {
		UpdateScale();
		Invalidate();
	} else
	if (e.Is<Timeline::Event>()) {
		Invalidate();
	} else
		Container::ProcessEvent(e);
}

void TimeRuler::HandleLeftDown(unsigned int flags, const CPoint & p)
{
	if (!m_Timeline.IsEmpty())
		m_Timeline.SetCurrentFrame(Round(m_Timeline.XToTime(p.x)));
	My::Container::HandleLeftDown(flags, p);
	
	// Simulate another click & drag:
	m_Thumb.HandleLeftDown(flags, p - m_Thumb.Offset());
	m_Thumb.HandleMouseMove(flags, p - m_Thumb.Offset());
}

void TimeRuler::HandleLeftUp(unsigned int flags, const CPoint & p)
{
	if (!m_Timeline.IsEmpty())
		m_Timeline.SetCurrentFrame(m_Timeline.GetCurrentFrame());
	My::Container::HandleLeftUp(flags, p);
}

void TimeRuler::HandlePaint(CDC * pDC, const CRect & r)
{
	My::SaveObjects save(pDC);

	CRect rClient = ClientRect();
	CBrush bFace; bFace.CreateSysColorBrush(COLOR_BTNFACE);	
	pDC->FillRect(&rClient, &bFace);

	CPen pBlack; pBlack.CreateStockObject(BLACK_PEN);
	CPen pLight(PS_SOLID, 1, GetSysColor(COLOR_BTNHILIGHT));
	CPen pShadow(PS_SOLID, 1, GetSysColor(COLOR_BTNSHADOW));

	pDC->SelectObject(&pLight);
	pDC->MoveTo(rClient.left, rClient.top);
	pDC->LineTo(rClient.right, rClient.top);
	pDC->SelectObject(&pShadow);
	pDC->MoveTo(rClient.left, rClient.bottom - 1);
	pDC->LineTo(rClient.right, rClient.bottom - 1);

	if (m_Timeline.IsEmpty())
		return;

	rClient.DeflateRect(0, 1);
	ClipRect clip(this, pDC, rClient);

	CBrush bViolet(RGB(242, 242, 255));

	CRect rCurrent(rClient);
	rCurrent.left = rClient.left + m_Timeline.TimeToX(m_Timeline.GetCurrentFrame());
	rCurrent.right = rClient.left + m_Timeline.TimeToX(m_Timeline.GetCurrentFrame() + 1);
	pDC->FillRect(&rCurrent, &bViolet);

	pDC->SelectObject(&pBlack);
	pDC->SetTextColor(RGB(0, 0, 0));
	pDC->SetBkMode(TRANSPARENT);

	Span timeSpan = m_Timeline.GetSequence()->GetTimeSpan();
	int fmin = m_Span * int(floor(m_Timeline.XToTime(rClient.left) / m_Span));
	if (fmin < timeSpan.first)
		fmin = timeSpan.first;
	int fmax = m_Span * int(ceil(m_Timeline.XToTime(rClient.right) / m_Span));
	if (fmax > timeSpan.last)
		fmax = timeSpan.last;

	SelectFont(pDC);

	if (m_Span > 0)
		for (int f = fmin; f <= fmax; f += m_Span) {
			int x = rClient.left + m_Timeline.TimeToX(f);
			pDC->MoveTo(x, rClient.top + 1);
			pDC->LineTo(x, rClient.top + 4);
			CString label = GetLabel(f);
			int tw = pDC->GetTextExtent(label).cx;
			pDC->TextOut(x - tw / 2, rClient.top + 4, label);
		}
}

} // My